module(...,package.seeall)
pcall(require,'ShinyLua')
pcall(require,'profiler')
require "ansicolors"
require'zdc.showutil'
local nodeapi=require'zdc.nodeapi'
local errtbl=require'zdc.errtbl'
local errdisp=errtbl.GetErrStr
local getdoc=require'zdc.doc'.get_doc
require'zdc.cmdlua'
P=require'zdc.cmdparse'
local clock=os.clock
local select=select
local Cauto = P.Cauto
local Cargsep = P.Cargsep
local Cnode = P.Cnode
local Cfield = P.Cfield
local Csubnode =P.Csubnode
local Csubnoexist=P.Csubnoexist
local Cval = P.Cval
local Cmc = P.Cmc
local Cautov=P.Cautov
local Ct=P.Ct
local Cp=P.Cp
local Ctable=P.Ctable
local Carg = P.Carg
local Cact = P.Cact
local Ctk=P.Ctk


local UI_IND='  '

local function fixwidthstr(str,w)
	local l=#str
	if l>=w then return str end
	return str .. string.rep(' ',w-l)
end

local _clicmd = {}
local _mtkey = {}
local _clicmd_mt = {
	__metatable = _mtkey,
	__index = _clicmd
}

--mode list
--[[
OP_cmdnode : operation mode, can run 'confingure,help,quit/exit,show'
CF_cmdnode : configuration mode, can run 'commit,create,delete,edit,quit/exit,help,set,save,load,show,top,up'
--]]
OP_cmdnode='operate'
CF_cmdnode='config'


function new(dal,rootnode,io,logger)
	local n = setmetatable({_io=io,dal=dal,logger=logger},_clicmd_mt)
	n._curmode=OP_cmdnode
	n._capture=nil
	n.rootnode=rootnode
	n._curlvl=rootnode
	n._cbmap={}
	n.showcpu=true
	--n:_setuser('admin')
	n.user=nil
	return n
end

function _clicmd:logop(usr,addr,cmd,r1,r2)
	do return end
	if r1~=nil then r1 = tostring(r1) end
	self.logger:info(tostring(user) .. '@' .. tostring(addr) .. '\t' .. cmd .. '\r\n' .. tostring(r1) .. ((r2 and ('\t' .. tostring(r2))) or ''))
end

function _clicmd:_put(...)
	self._io:put(...)
end

function _clicmd:_print(...)
	for i = 1,select("#",...) do
		if i>1 then
			self._io:put('\t')
		end
		self._io:put(tostring(select(i,...)))
	end
	self._io:put('\r\n')
end

local RED_ERROR=ansicolors.magenta 'ERROR'
local RED_EXCEPTION=ansicolors.magenta 'EXCEPTION'
-- r : true for ok, false or nil for error
-- msg : string to show
function _clicmd:_show_pcall_result(rp,r,msg)
	if rp then
		self._io:put((r and 'SUCCESS')or RED_ERROR)
		local t=type(msg)
		if t=='table' then
			for _,v in ipairs(msg) do
				self._io:put('\n\t',tostring(v))
			end
		elseif msg~=nil then
			self._io:put(', ',errdisp(tostring(msg)))
		end
		self._io:put('\n')
		return r,msg
	else
		if self._curmode==CF_cmdnode then
			self._cfghaserror=true
		end
		self._io:put(RED_EXCEPTION)
		if r then
			self._io:put(', ',tostring(r))
		end
		self._io:put('\n')
		return rp,r
	end
end

function _clicmd:_splite_cmd(cmd,n)
	local r={}
	for sub in string.gmatch(cmd,'[^%s]+') do
		if #sub>0 then
			r[#r]=sub
		end
	end
end

function _clicmd:_set_lvl(id)
	if self.dal:GetNode(id) then 
		self._curlvl = id
		return true
	else
		return false,"not find " .. tostring(id) 
	end
end

local cmdtable={}
local prompttable={}
local checktable={}

function installprompt(mode,pf)
	prompttable[mode]=pf
end

function installcheck(mode,pf)
	checktable[mode]=pf
end

function _clicmd:_put_prompt()
	if self._capture then return end
	local pf=prompttable[self._curmode]
	if SLAVE_MODE then
		self:_put(ansicolors.magenta'(SLAVE)')
	end
	if pf then
		if type(pf)=='function' then
			self:_put(pf(self))
		else
			self:_put(pf)
		end
	else
		self:_put('# ')
	end
end

function installcmd(mode,cmd)
	local t=cmdtable[mode]
	if not t then
		t={}
		cmdtable[mode]=t
	end
	if t[cmd.name] then
		--cmd has exist
	end
	t[cmd.name]=cmd
end

function installfixcmd(t,cmd)
	if not t then
		t={}
	end
	if t[cmd.name] then
		--cmd has exist
	end
	t[cmd.name]=cmd
end

local function _matchcmd(tab,cmd)
	local m={}
	for k,v in pairs(tab) do
		if string.find(k,cmd,1,true)==1 then
			m[#m+1]=k
		end
	end
	local l=#m
	if l==0 then
		return nil,nil,m
	elseif l==1 then
		return tab[m[1]],(#m[1]>#cmd and m[1]),m
	else
		return nil,m,m
	end
end

function _clicmd:_isend()
	return self._curmode=='END'
end

function _clicmd:_close()
	if self.dal.transowner==self then
		self.dal:AbortTA()
		self.dal.transowner = nil
	else
	end
	for k,v in pairs(self._cbmap) do
		--clear callback function
	end
	self._curmode='END'
end

function _clicmd:_setuser(user,addr)
	self.user=user
	self.useraddr=addr
	if not self.user then self:_close() end
end

function _clicmd:docmd(line,auto)
	if self:_isend() then
		self:_put(ansicolors.magenta'SESSION ENDED\n')
		return 
	end
	if self._capture then
		return self._capture(self,line,auto)
	end

	if self.showcpu then
		if self.showcpu=='shiny' and shiny then
			shiny.clear()
			shiny.start()
			local r=self:_docmd(line,auto)
			shiny.stop()
			shiny.update()
			shiny.output([[shinypro.txt]])
			return r
		elseif self.showcpu=='profiler' and profiler then
			profiler.start()
			local r=self:_docmd(line,auto)
			profiler.stop()
			return r
		else
			local s=clock()
			local r=self:_docmd(line,auto)
			self:_put(tostring(clock()-s),'s, ',collectgarbage("count"),'k\n')
			return r
		end
	else
		return self:_docmd(line,auto)
	end
end
function _clicmd:_set_capture(f)
	self._capture=f
end
function _clicmd:_docmd(line,auto,fixcmd)
	if auto and string.byte(line,#line)~=9 then line=line .. '\t' end
	local cmdtablenil
	local cmd,arg = string.match(line,'^%s*(%a+)[ ]*([^\r\n]*)')
	--print(cmd,arg)
	if not cmd then return  end
	local ctable=fixcmd or cmdtable[self._curmode] or {}

	local k
	local cmdfun,tab=_matchcmd(ctable,cmd:lower())
	if not cmdfun then
		if tab then
			self:_put('Possible cmd:\n')
			for _,v in ipairs(tab) do
				self:_put(UI_IND,v,'\n')
			end
		else
			self:_put(ansicolors.magenta'ERROR,Unknow cmd\n')
		end
		return
	end
	if tab then
		self:_put('[Guess cmd: ',tab,']\n')
	end

	if auto then
		self:_put(UI_IND,fixwidthstr(cmdfun.name,14),': ',cmdfun.desc or  cmdfun.help or '','\n') 
	end
	
	if cmdfun.parse then
		local parg,endpos=lpeg.match(cmdfun.parse,arg)
		if not parg then
			self:_put(ansicolors.magenta'ERROR,Unknow format\n')
			return
		end
		if endpos then
			local rs=arg:sub(endpos)
			if rs and string.find(rs,"%S") then
				self:_put(ansicolors.magenta'ERROR,Has unknow extra inputs?\n')
				return
			end
		end
		local e,rt=P.parseresult(self.dal,self,parg)
		if e==0 then
			if auto then
				self:_put('<cr>','\n')
				local nl = string.sub(line,1,#line-1)
				return nl
			end
			if checktable[self._curmode] then
				local r,e=checktable[self._curmode](self,cmdfun)
				if not r then
					self:_show_pcall_result(true,r,e)
					return
				end
			end
			self:logop(self.user and self.user.name,self.useraddr,line,self:_show_pcall_result(xpcall(function () return cmdfun.fun(self,rt) end,debug.traceback)))
			return
		elseif e<0 then
			self:_put(ansicolors.magenta(rt or 'Unknow format'),'\n')
			return
		elseif e==1 then -- match one
			local nl = string.sub(line,1,#line-1) .. rt
			return nl
		elseif e==2 then -- match one
			for _,v in ipairs(rt) do
				local ty=type(v)
				if ty=='table' then
					self:_print(unpack(v))
				elseif ty=='string' then
					self:_put(v,'\n')
				else
					--TODO:
					self:_put(tostring(v),'\n')
				end
			end
			local nl = string.sub(line,1,#line-1)
			return nl
		else
			self:_put(ansicolors.magenta'ERROR,Unknow format',rt or '','\n')
			local nl = string.sub(line,1,#line-1)
			return nl
		end
		arg=rt
	else
		if auto then
			self:_put('\t','<cr>','\n')
			local nl = string.sub(line,1,#line-1)
			return nl
		end
		if checktable[self._curmode] then
			local r,e=checktable[self._curmode](self,cmdfun)
			if not r then
				self:_show_pcall_result(true,r,e)
				return
			end
		end
		self:logop(self.user and self.user.name,self.useraddr,line,self:_show_pcall_result(xpcall(function () return cmdfun.fun(self,arg) end,debug.traceback)))
	end
end

function _clicmd:_cmd_file(cmdtb,fname,extname)
	local ful=fname
	if extname then ful=ful .. extname end
	local f = io.open(ful,'r')
	if f then
		for l in f:lines('*a') do
			local nl=string.match(l,'^(.-)\r*$')
			self:_put(nl,'\n')
			self:_docmd(nl,nil,cmdtb)
		end
		f:close()
		return true
	else
		return nil,"can't open file " .. ful .. '\n'
	end
end

function _clicmd:_cmdlua_file(fname)
	local ful=fname
	local f = io.open(ful,'r')
	local cl
	if f then
		cl=f:read('*a')		
		f:close()
		return zdc.sandbox.fast_do(zdc.cmdlua.new(self),cl)
	else
		return nil,"can't open file " .. ful .. '\n'
	end
end
function _clicmd:_cmdlua_line(cl)
	return zdc.sandbox.fast_do(zdc.cmdlua.new(self),cl)
end


local op_configure={
name='configure',
help=[[Switch to configuration mode]],
fun=function(env,arg)
	return env:configure()
end
}
function _clicmd:configure()
	if SLAVE_MODE then return nil, "Can't enter configuration mode in slave mode" end
	if self._curmode~=CF_cmdnode then
		if not self.dal.transowner then
			self.dal.transowner = self
			self._curmode=CF_cmdnode
			self._cfghaserror=false
			return true,'Entering configuration mode.'
		else
			return nil,"Can't enter configuration mode."
		end
	end
	return
end

installcmd(OP_cmdnode,op_configure)
local cf_err_permit={exit=true,abort=true,dropdb=true,help=true,show=true}
function cf_checkerr(env,cmd)
	if env._curmode==CF_cmdnode and env._cfghaserror then
		if cf_err_permit[cmd.name] then return true else return false,'config in error stat, you must do abort' end
	else
		return true
	end
end

installcheck(CF_cmdnode,cf_checkerr)

local op_quit={
name='quit',
help=[[Quit from this session]],
fun=function(env,arg)
	return env:quit()
end
}

installcmd(OP_cmdnode,op_quit)
function _clicmd:quit()
	self:_close()
	return true,'bye'
end

local cf_file_cmd={}

local cf_luacmd={
name='luacmd',
help=[[quick execute a lua command]],
fun=function(env,arg)
	return env:_cmdlua_line(arg)
end
}
installcmd(CF_cmdnode,cf_luacmd)
installfixcmd(cf_file_cmd,cf_luacmd)

local cf_file={
name='file',
help=[[execute config file!]],
fun=function(env,arg)
	local fname=arg
	if not fname or #fname==0 then
		return nil,'bad file name.'
	end
	return env:_cmd_file(cf_file_cmd,fname,'.config')
end
}
installcmd(CF_cmdnode,cf_file)
installfixcmd(cf_file_cmd,cf_file)

local cf_luafile={
name='luafile',
help=[[execute config luafile!]],
fun=function(env,arg)
	local fname=arg
	if not fname or #fname==0 then
		return nil,'bad file name.'
	end
	return env:_cmdlua_file(fname)
end
}
installcmd(CF_cmdnode,cf_luafile)
installfixcmd(cf_file_cmd,cf_luafile)

installprompt(CF_cmdnode,function (env)
	if env._cfghaserror then
		return '[edit ',env._curlvl,']\nCFG ERROR STAT# '
	else
		return '[edit ',env._curlvl,']\nCFG# '
	end
end)

local cf_dropdb={
name='dropdb',
help=[[delete all config data from db!]],
fun=function(env,arg)
	env._cfghaserror=false
	env.dal:DropAll({label="cmd dropdb"})
	return true
end
}
installcmd(CF_cmdnode,cf_dropdb)


installfixcmd(cf_file_cmd,cf_dropdb)

local cf_commit={
name='commit',
help=[[Commit the current set of changes]],
fun=function(env,arg)
	return env:commit()
end
}
installcmd(CF_cmdnode,cf_commit)
installfixcmd(cf_file_cmd,cf_commit)

function _clicmd:commit()
	if SLAVE_MODE then
		self:exit('discard')
		return nil, "Can't commit in slave mode"
	end
	if self._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		self.dal:CommitTA({bsave=true,nil,nil,label=os.date("!%Y-%m-%d %H:%M:%S")})
		return true
	end
end

local cf_abort={
name='abort',
help=[[abort the current set of changes]],
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		env._cfghaserror=false
		env.dal:AbortTA()
		env.dal:ResetCache(true)
		return true
	end
end
}
installcmd(CF_cmdnode,cf_abort)
installfixcmd(cf_file_cmd,cf_abort)

local cf_mknode={
name='mknode',
help=[[make a new element]],
desc="mknode 'nodeid' [{default values}]",
parse=Ct(Cautov('val')+(Cval*(Cauto+(Cargsep*(Cautov('val')+(Cval*(Cauto^-1)))))))*Cp(),
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local id,data=arg[1],arg[2]
		local r1,r2=nodeapi.makenode(env.user,env.dal,id,data)
		if not r1 then
			env._cfghaserror=true
		end
		return r1,r2
	end
end
}
installcmd(CF_cmdnode,cf_mknode)
installfixcmd(cf_file_cmd,cf_mknode)

local cf_rmnode={
name='rmnode',
help=[[remove a configuration element]],
desc="rmnode 'node' ['nodeep']",
parse=Ct(Cautov('val')+(Cval*(Cauto+(Cargsep*(Cautov('val')+(Cval*(Cauto^-1)))))^0))*Cp(),
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local pa,sub=arg[1],arg[2]
		local r1,r2=nodeapi.removenode(env.user,env.dal,pa,not sub)
		if not r1 then
			env._cfghaserror=true
		end
		return r1,r2
	end
end
}
installcmd(CF_cmdnode,cf_rmnode)
installfixcmd(cf_file_cmd,cf_rmnode)

local cf_create={
name='create',
help=[[Create a new element]],
desc="create 'parent node' 'sub node to create' [node type] [{default values}]",
parse=Ct(Cautov('node')+Cnode*(Cauto+(Cargsep*(Cautov('subnoexist')+(Csubnoexist*((Cauto+(Cargsep*(Cautov('mc')+Cmc*(Cauto^-1)))^-1)^-1)*((Cauto+(Cargsep*(Cautov('table')+Ctable*(Cauto^-1)))^-1)))))))*Cp(),
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local pa,sub,mc,dft=arg[1],arg[2],arg[3],arg[4]
		if type(mc)=='table' then dft,mc=mc,dft end
		local r1,r2=nodeapi.createsub(env.user,env.dal,pa,sub,mc,dft)
		if not r1 then
			env._cfghaserror=true
		end
		return r1,r2
	end
end
}
installcmd(CF_cmdnode,cf_create)
installfixcmd(cf_file_cmd,cf_create)

local cf_replace={
name='replace',
help=[[Replace a sub node element]],
desc="replace 'parent node' 'sub node to replace' [node type] {default values}",
parse=Ct(Cautov('node')+Cnode*(Cauto+(Cargsep*(Cautov('subnode')+(Csubnode*((Cauto+(Cargsep*(Cautov('mc')+Cmc*(Cauto^-1)))^-1)^-1)*((Cauto+(Cargsep*(Cautov('table')+Ctable*(Cauto^-1))))))))))*Cp(),
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local pa,sub,mc,dft=arg[1],arg[2],arg[3],arg[4]
		if type(mc)=='table' then dft,mc=mc,dft end
		local r1,r2=nodeapi.replacesub(env.user,env.dal,pa,sub,mc,dft)
		if not r1 then
			env._cfghaserror=true
		end
		return r1,r2
	end
end
}
installcmd(CF_cmdnode,cf_replace)
installfixcmd(cf_file_cmd,cf_replace)

local cf_mset={
name='mset',
help=[[mutil set node elements, maybe half success]],
desc="mset 'parent node' 'sub node to mset' {default values}",
parse=Ct(Cautov('node')+Cnode*(Cauto+(Cargsep*(Cautov('subnode')+(Csubnode*((Cauto+(Cargsep*(Cautov('mc')+Cmc*(Cauto^-1)))^-1)^-1)*((Cauto+(Cargsep*(Cautov('table')+Ctable*(Cauto^-1))))))))))*Cp(),
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local pa,sub,mc,dft=arg[1],arg[2],arg[3],arg[4]
		if type(mc)=='table' then dft,mc=mc,dft end
		local r1,r2=nodeapi.updatesub(env.user,env.dal,pa,sub,mc,dft)
		if not r1 then
			env._cfghaserror=true
		end
		return r1,r2
	end
end
}
installcmd(CF_cmdnode,cf_mset)
installfixcmd(cf_file_cmd,cf_mset)

local cf_delete={
name='delete',
help=[[Delete a configuration element]],
desc="delete 'parent node' 'sub node to delete'",
parse=Ct(Cautov('node')+Cnode*(Cauto+(Cargsep*(Cautov('subnode')+(Csubnode*(Cauto^-1))))))*Cp(),
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local pa,sub=arg[1],arg[2]
		local r1,r2=nodeapi.delsub(env.user,env.dal,pa,sub)
		if not r1 then
			env._cfghaserror=true
		end
		return r1,r2
	end
end
}
installcmd(CF_cmdnode,cf_delete)
installfixcmd(cf_file_cmd,cf_delete)

local cf_delsubs={
name='delsubs',
help=[[Delete some configuration element]],
desc="delete 'parent node' '{sub node to delete}'",
parse=Ct(Cautov('node')+Cnode*(Cauto+(Cargsep*(Cautov('table')+(Ctable*(Cauto^-1))))))*Cp(),
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local pa,sub=arg[1],arg[2]
		local r1,r2=nodeapi.mdelsub(env.user,env.dal,pa,sub)
		if not r1 then
			env._cfghaserror=true
		end
		return r1,r2
	end
end
}
installcmd(CF_cmdnode,cf_delsubs)
installfixcmd(cf_file_cmd,cf_delsubs)

cf_edit={
name='edit',
help=[[Edit a sub-element]],
desc="edit 'node'",
parse=Ct(Cautov('node')+Cnode*Cauto^-1)*Cp(),
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local pa=arg[1]
		local r1,r2=env:_set_lvl(pa.__id)
		if not r1 then
			env._cfghaserror=true
		end
		return r1,r2
	end
end
}
installcmd(CF_cmdnode,cf_edit)
installfixcmd(cf_file_cmd,cf_edit)

local cf_set={
name='set',
help=[[Set the value of a parameter or create a new field]],
desc="set 'node' 'field' 'value'",
parse=Ct(Cautov('node')+Cnode*(Cauto+(Cargsep*(Cautov('field')+(Cfield*(Cauto+(Cargsep*(Cautov('val')+(Cval*(Cauto^-1))))))))))*Cp(),
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local n,fd,val=arg[1],arg[2],arg[3]
		local r1,r2=nodeapi.set(env.user,env.dal,n,fd,val)
		if not r1 then
			env._cfghaserror=true
		end
		return r1,r2
	end
end
}
installcmd(CF_cmdnode,cf_set)
installfixcmd(cf_file_cmd,cf_set)

local cf_unset={
name='unset',
help=[[Unset the value of a field]],
desc="unset 'node' 'field1' '[field2 ...]'",
parse=Ct(Cautov('node')+Cnode*(Cauto+(Cargsep*(Cautov('field')+(Cfield*(Cauto+(Cargsep*(Cautov('field')+Cfield*(Cauto^-1)))^0))))))*Cp(),
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local n=arg[1]
		local r,e
		for i=2,#arg do
			r,e=nodeapi.unset(env.user,env.dal,n,arg[i])
			if not r then
				env._cfghaserror=true
				return r,e
			else
			    --env:_put('OK\n')			
			end
		end
	end
	return true
end
}
installcmd(CF_cmdnode,cf_unset)
installfixcmd(cf_file_cmd,cf_unset)

local cf_get={
name='get',
help=[[Get the value of a field]],
desc="get 'node' 'field1' '[field2 ...]'",
parse=Ct(Cautov('node')+Cnode*(Cauto+(Cargsep*(Cautov('field')+(Cfield*(Cauto+(Cargsep*(Cautov('field')+Cfield*(Cauto^-1)))^0))))))*Cp(),
fun=function(env,arg)
	if env._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local n=arg[1]
		local r,e
		local rs={}
		for i=2,#arg do
			r,e=nodeapi.get(env.user,env.dal,n,arg[i],true)
			if not r then
				env._cfghaserror=true
				return r,e
			else
				rs[#rs+1]=r;
				--env:_put(r)
			end
		end
		env:_put(table.concat(rs,'\t'))
		env:_put('\n');
		return rs
	end
end
}
installcmd(CF_cmdnode,cf_get)
installfixcmd(cf_file_cmd,cf_get)

local cf_exit={
name='exit',
help=[[Exit from this configuration level]],
fun=function(env,arg)
	return env:exit(arg)
end
}

installcmd(CF_cmdnode,cf_exit)
function _clicmd:exit(discard)
	if self._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		if discard == 'discard' then
			self.dal:AbortTA()
		elseif self.dal:InTA() then
			return nil,'Please commit the change or use exit discard to drop the change.'
		end
		self._curmode=OP_cmdnode
		self.dal.transowner=nil;
		self:_set_lvl('')
		return true
	end
end

local cf_top={
name='top',
help=[[Exit to top level of configuration]],
fun=function(env,arg)
	return env:top(arg)
end
}
installcmd(CF_cmdnode,cf_top)
installfixcmd(cf_file_cmd,cf_top)

function _clicmd:top(arg)
	if self._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		return self:_set_lvl(self.rootnode)
	end
end

local cf_up={
name='up',
help=[[Exit one level of configuration]],
fun=function(env,arg)
	return env:up(arg)
end
}
installcmd(CF_cmdnode,cf_up)
installfixcmd(cf_file_cmd,cf_up)

function _clicmd:up()
	if self._curmode~=CF_cmdnode then
		return nil,'Please Enter configuration mode.'
	else
		local parent_id, rest = string.match(self._curlvl, "^(.+)/(.-)$")
		if rest then
			return self:_set_lvl(parent_id or '')
		else
			--self:_put('Error, .\n')
		end
		return true
	end
end

local cf_show={
name='show',
help=[[Show the configuration (default values may be suppressed)]],
parse=Ct(Cautov('node')+Cnode*(Cauto+(Cargsep*(Cautov('Ctk')+Ctk*(Cauto^-1))))^0),
desc="show '[node]' '[all]' '[sub|lvl]'",
fun=function(env,arg)
	local pa,flag,flag2=arg[1],arg[2],arg[3]
	if pa then
		return env:show(pa,flag=='all' or flag2=='all',flag=='sub' or flag2=='sub' or tonumber(flag) or tonumber(flag2))
	else
		return nil,'No parent node.'
	end
end
}
installcmd(CF_cmdnode,cf_show)
installcmd(OP_cmdnode,cf_show)

function _clicmd:show(pa,inh,deep)
	zdc.showutil.ShowNode(pa,function (...) return self:_put(...) end,inh,deep)
	return true
end

local cf_count={
name='cnt',
help=[[count the node's child|subnodes]],
desc="cnt 'parentid' ['where']",
parse=Ct(Cautov('val')+(Cval*(Cauto+(Cargsep*(Cautov('val')+(Cval*(Cauto^-1))))^0)))*Cp(),
fun=function(env,arg)
	local id,where=arg[1],arg[2]
	local cnt=env.dal:GetChildCntEx(id,where)
	return true,cnt
end
}
installcmd(CF_cmdnode,cf_count)
installcmd(OP_cmdnode,cf_count)

local cf_dplcount={
name='dplcnt',
help=[[count the node's in one dpl by prefix]],
desc="dplcnt 'parentid' ['where']",
parse=Ct(Cautov('val')+(Cval*(Cauto+(Cargsep*(Cautov('val')+(Cval*(Cauto^-1))))^0)))*Cp(),
fun=function(env,arg)
	local id,where=arg[1],arg[2]
	local dpl=env.dal:GetDpl(id)
	if dpl then return true,dpl:_get_prefix_cnt_ex(id,where) end
	return nil,'no dpl find for '..tostring(id)
end
}
installcmd(CF_cmdnode,cf_dplcount)
installcmd(OP_cmdnode,cf_dplcount)

op_actinfo={
name='actinfo',
help='show function info',
desc="actinfo 'node' ['function']",
parse=Ct(Cautov('node')+Cnode*((Cauto+(Cargsep*(Cautov('act')+Cact)))^0))*Cp(),
fun=function(env,arg)
	local n,act=arg[1],arg[2]
	if act then
		local actdef=n:__get_act_define(act)
		env:_put(UI_IND,getdoc(actdef._doc,'disp') or '',':\n',getdoc(actdef._doc,'desc') or '')
	else
		local as=n:__get_acts()
		for _,k in pairs(as) do
			local v=n:__get_act_define(k)
			env:_put(fixwidthstr(k,16),'\t',getdoc(v._doc,'disp') or '','\n')
		end
	end
	
	return true
end
}
installcmd(OP_cmdnode,op_actinfo)
installcmd(CF_cmdnode,op_actinfo)

op_call={
name='call',
help='call function',
desc="call 'node' 'function' arg1 arg2 ...",
parse=Ct(Cautov('node')+Cnode*(Cauto+(Cargsep*(Cautov('act')+(Cact*(Cauto+(Cargsep*(Cautov('arg')+(Carg*(Cauto^-1)))))^0)))))*Cp(),
fun=function(env,arg)
	local n,act=arg[1],arg[2]
	local actdef=n:__get_act_define(act)
	local function cb(ok,...)
		if env:_isend() then return end
		if actdef.async then
			env:_put('\n~~async result:\n')
		end
		if not ok or ok < 0 then
			env:_print(ok,...)
		else
			if actdef and actdef.clishow then
				env:_put(actdef.clishow(...))
			else
				--env:_print(ok,...)
				env:_put(zdc.doc.str_print(...))
			end			
		end
		if actdef.async then
			env:_put('\n~~async end\n')
		end
		env:_put_prompt();
		if actdef and actdef.cbret then
			return actdef.cbret(ok,...)
		end
	end
	local r,e=nodeapi.asynccall(env.user,env.dal,cb,unpack(arg))
	if env._curmode==CF_cmdnode then
		if not r and actdef.env=='trans' then
			env._cfghaserror=true
		end
	end
	--[[TODO: delete callback when end.
	local cbidx=tonumber(r)
	if cbidx and cbidx > 0 then
		env._cbmap[cb]=cbidx
	end
	--]]
	return r,e
end
}
installcmd(OP_cmdnode,op_call)
installcmd(CF_cmdnode,op_call)
installfixcmd(cf_file_cmd,op_call)

op_help={
name='help',
help='Provide help with commands',
fun=function(env,arg)
	local cmd=string.match(arg,'^%s*([^%s]+)%s*')
	return env:help(cmd)
end
}
installcmd(OP_cmdnode,op_help)
installcmd(CF_cmdnode,op_help)
function _clicmd:help(arg)
	local ctable=cmdtable[self._curmode] or {}
	if not arg or arg=='' then
		for k,v in pairs(ctable) do
			self:_put(UI_IND,fixwidthstr(v.name,14),': ',v.help or '','\n')
		end
	else
		local _,_,kc = _matchcmd(ctable,arg)
		for k,v in pairs(kc) do
			v=ctable[v]
			self:_put(UI_IND,fixwidthstr(v.name,14),': ',v.desc or  v.help or '','\n')
		end
	end
	return true
end

installprompt('debug','DBG# ')

installcmd('debug',op_help)

local op_debug={
name='debug',
help=[[Switch to debug mode]],
fun=function(env,arg)
	if env._curmode~='debug' then
		env._curmode='debug'
		env:_put('Entering debug mode.\n')
	end
	return true
end
}
installcmd(OP_cmdnode,op_debug)

local debug_exit={
name='exit',
help=[[Exit from this debug level]],
fun=function(env,arg)
	if env._curmode~=OP_cmdnode then
		env._curmode=OP_cmdnode
		env:_put('Ok.\n')
		return true
	end 
end
}
installcmd('debug',debug_exit)

local debug_cmdcpu={
name='cmdcpu',
help=[[Show command's cpu profile]],
fun=function(env,arg)
	if arg=='off' or arg=='' then 
		env.showcpu=nil
	else
		env.showcpu=arg
	end
	return true
end
}
installcmd('debug',debug_cmdcpu)

local debug_mem={
name='mem',
help=[[Collect garbage and Show memory of odc vm]],
fun=function(env,arg)
	collectgarbage("collect")
	local k=collectgarbage("count")
	env:_put('using:',tostring(k),'KB\n')
	return true
end
}
installcmd('debug',debug_mem)


local debug_luacli={
name='luaenv',
help=[[enter lua command env]],
fun=function(env,arg)
	require'coroutine'

	local function sktread()
		return coroutine.yield()
	end
	local function doone()
		local iline = ""
		local iprefix = ""
		local icode = ""
		local icompiled = nil
		local ierrormsg = nil
		local ichunkname = "istdin"
	  local iincomplete
	  local pat = "'<eof>'$"
	  env:_put("i> ")
	  iline = sktread()
	  if not iline or iline=='!quit' then return false end                -- eof
	  if string.sub(iline,1,1)=="=" then
	    iprefix = "="
	    icode = "return " .. string.sub(iline,2)
	  else
	    iprefix = ""
	    icode = iline
	  end
	  while true do
	    icompiled,ierrormsg=loadstring(icode,"(interactive)")
	    iincomplete = ierrormsg and string.find(ierrormsg,pat)
	    if not iincomplete then break end
	    env:_put("i>> ")
	    iline = sktread()
	    if not iline then return false end              -- eof
	    icode = icode .. "\n" .. iline
	  end
	  if ierrormsg then
	    env:_put(ierrormsg,'\r\n')
	  else
	  	setfenv(icompiled,setmetatable({print=function (...) env:_print(...) end},{__index=_G}))
	    local iresult
	    if iprefix == "=" then env:_print(select(2,pcall(icompiled))) end
	
	  	if iprefix == ""  then 
	  		local st,msg = pcall(icompiled)
	  		if not st then
	  			env:_put(msg)
	  		end 
	  	end
	  end
	  return true
	end
	local th=coroutine.create(function()
		while doone(req)do
		end
		env:_put("Now leave lua command env\n");
		env:_set_capture(nil)
	end)
	local r,a=coroutine.resume(th)
	if not r then
		env:_set_capture(nil)
		env:_put("ERROR:",a,'\n');
		return 
	end
	env:_set_capture(function (env,line,auto)
		local r,a=coroutine.resume(th,line)
		if not r then
			env:_set_capture(nil)
			env:_put("ERROR:",a,'\n');
		end
	end)
	return true,"Now enter lua command env, use !quit to quit"
end
}
installcmd('debug',debug_luacli)